/*
 * AGENT SHERPA
 *  
 * Assignement 2 of the Agent Oriented Software Engineering Course
 * M.Sc. in Advanced Software Engineering (Part-time)
 * University College Dublin www.ucd.ie
 *
 * Copyright (c) 2007 Chris Walsh, Aidan Morrisy and Iain Hull.
 * All rights reserved.
 * 
 * Filename: Main.aps
 * Author:   iain
 * Date:     06 February 2008
 */

package org.agentsherpa.ui;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import javax.imageio.ImageIO;

import org.agentsherpa.model.CellType;
import org.agentsherpa.model.Drone;
import org.agentsherpa.model.GlobalMap;
import org.agentsherpa.model.Ied;
import org.agentsherpa.model.Point;
import org.agentsherpa.model.SimulatorModel;

public class MapComponent extends Component implements Observer {
    private static final long serialVersionUID = -8329989911441325285L;

    private SimulatorModel simulator;
    private int zoom = 20;
    private BufferedImage roadImage;
    private BufferedImage backgroundImage;
    
    private Map<Point, CellType> knownRoad = new HashMap<Point, CellType>();
    private List<Drone> drones = new ArrayList<Drone>();
    private List<Ied> ieds = new ArrayList<Ied>();
    
    
    public MapComponent(SimulatorModel simulator, int zoom, URL backgroundUrl) {
        this.zoom = zoom;
        this.simulator = simulator;
        try {
            this.backgroundImage = ImageIO.read(backgroundUrl);
        }
        catch (IOException e) {
            this.backgroundImage = null;
        }
        this.simulator.addObserver(this);
        simulator.getKnownRoad(knownRoad);
        createRoadImage(simulator.getMap(), zoom, knownRoad);
        this.setPreferredSize(new Dimension(roadImage.getWidth(), roadImage.getHeight()));
    }
    
    /**
     * Construct a Buffered Image of the road.  This speeds rendering of the 
     * road in the paint method.
     *  
     * @param map
     * @param zoom
     * @return
     */
    private void createRoadImage(GlobalMap map, int zoom, Map<Point, CellType> known) {
    	if (roadImage == null) {
        	roadImage = new BufferedImage(map.getWidth() * zoom, map.getHeight() * zoom, BufferedImage.TYPE_INT_ARGB);
    	}
    	
        Graphics2D g2 = roadImage.createGraphics();

        for(Point point : map.getRoadPoints()) {
                  int s = Math.max(zoom - 1, 1);
                  g2.setColor(roadColor(known.get(point)));
                  g2.fillRect(point.getX()*zoom, point.getY()*zoom, s, s);                  
        }
    }
    
    private static Color roadColor(CellType type) {
		if (type == CellType.CLEARED || type == CellType.DRONE || type == CellType.VISITED) {
			return Color.GREEN;
		}
		if (type == CellType.ROAD) {
			return Color.BLUE;
		}
		return  Color.YELLOW;
	}

	@Override
    public void paint(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.drawImage(
                backgroundImage, 
                new AffineTransformOp(
                        AffineTransform.getScaleInstance(zoom, zoom), 
                        AffineTransformOp.TYPE_NEAREST_NEIGHBOR ), 
                0, 0);
        
        paintRoad(g2);
        paintIeds(g2);
        paintDrones(g2);
    }

    /**
     * Paint all the Drones.
     * 
     * @param g2
     */
    private void paintDrones(Graphics2D g2) {
    	drones.clear();
    	simulator.getDrones(drones);
    	
        for(Drone drone : drones) {
        	if (drone.getStatus() == Drone.Status.Alive) {
	            // System.out.println("Drawing drone " + drone);
	            BufferedImage icon = Icons.instance().drone();            
	            Rectangle rec  = iconRectangle(drone.getLocation(), icon);
	            g2.drawImage(icon, null, rec.x, rec.y);
        	}
        }
    }

    /**
     * Paint all the Drones.
     * 
     * @param g2
     */
    private void paintIeds(Graphics2D g2) {
    	ieds.clear();
    	simulator.getIeds(ieds);
        for(Ied ied : ieds) {
            // System.out.println("Drawing ied " + ied);
            BufferedImage icon = Icons.instance().iedByStatus(ied.getStatus());
            Rectangle rec  = iconRectangle(ied.getLocation(), icon);
            g2.drawImage(icon, null, rec.x, rec.y);
        }
    }
    
    
    /**
     * Paint the road.
     * 
     * @param g2
     */
    private void paintRoad(Graphics2D g2) {
        Composite oldComp = g2.getComposite();
        try {
            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
            g2.drawImage(roadImage, null, 0, 0);
        }
        finally {
            g2.setComposite(oldComp);
        }
    }

    /**
     * Returns the rectangle that marks the boundary of the Drone
     * image on the screen.
     * 
     * @param location the location of the Drone.
     * @return
     */
    private Rectangle iconRectangle(Point location, BufferedImage icon) {
        int width = icon.getWidth();
        int height = icon.getHeight();
        int x = originOfCenteredRectangle(location.getX(), width);
        int y = originOfCenteredRectangle(location.getY(), height);
        return new Rectangle(x, y, width, height);
    }

    private int originOfCenteredRectangle(int pos, int size) {
        return (pos * zoom) - ((size - zoom)/2);
    }

    
    /**
     * Update the display to include model updates.  This is called by the 
     * SimulatorModel when it changes.
     */
    public void update(Observable o, Object arg) {
        if (arg instanceof SimulatorModel.DroneChange) {
            updateDrone((SimulatorModel.DroneChange)arg);
        }
        if (arg instanceof Set)
        	updatePoints((Set<Point>) arg);
        else {
            repaint();
        }
    }

    private void updatePoints(Set<Point> points) {
    	simulator.getKnownRoad(knownRoad);
    	createRoadImage(simulator.getMap(), zoom, knownRoad);
    	
    	Rectangle rec = null;
    	for (Point p : points) {
    		rec = addPointToRectangle(rec, p);    		
    	}
    	repaint(rec.x, rec.y, rec.width, rec.height);
	}

	private Rectangle addPointToRectangle(Rectangle rec, Point p) {
		Rectangle r = new Rectangle(p.getX(), p.getY(), zoom, zoom);
		if (rec == null) {
			return r;
		}
		rec.add(r);
		return rec;
	}

	/**
     * Update the specific area of the image effected my the movement of a
     * Drone
     * 
     * @param dc
     */
    private void updateDrone(SimulatorModel.DroneChange dc) {
        BufferedImage droneImg = Icons.instance().drone();
        Rectangle rec = iconRectangle(dc.getDrone().getLocation(), droneImg);
        if (dc.getOldLocation() != null) {
            rec.add(iconRectangle(dc.getOldLocation(),droneImg));
        }
        repaint(rec.x, rec.y, rec.width, rec.height);
    }
}
